/*
** KSS driver for smse2k
** by unknownfile, 2009
**
** References and code sources:
** - MSXplug by Mitsutaka Okazaki
** - KSS Specifications by mamiya. http://www.proc.org.tohoku.ac.jp/befis/download/nezplug/kssspec.txt
**
** TODO:
** - SMS support
** - FM support (YM2413, MSX-AUDIO, etc)
** - Port over other features
*/

#include "smse2kincludes.h"
#include "SoundDeviceFactory.h"
#include "CPUFactory.h"
#include "KSSFile.h"
#include "KSS_MMU.h"

#include "SCC.h"
#include "KSSDriver.h"


int CKSSDriver::Load(const char* fn) {
	CKSSFile* file = new CKSSFile;
	

	if (file->Open(fn)) {
		Msg("KSS: File load failed\n");

		delete[] file;
		return 1;
	}

	// Prepare to copy memory over
	//CMemory* tempblk = file->GetMainCode();

	header = file->GetHeader();
	// Allocate memory blocks
	main_code = file->GetMainCode();
	bank_code = file->GetBankedData();
//	main_code->CopyMemory( tempblk->GetRawMemory(), tempblk->GetSize());


	return 0;
}


static UINT8 wrtpsg[8] = { 0xd3, 0xa0, 0xf5, 0x7B, 0xd3, 0xa1, 0xf1, 0xc9 };
static UINT8 rdpsg [5] = { 0xd3, 0xa0, 0xdb, 0xa2, 0xc9 };
static UINT8 j_wrtpsg[3] = { 0xc3, 0x01, 0x00 };
static UINT8 j_rdpsg[3] = { 0xc3, 0x09, 0x00 };

#define KSS_DRIVER_START 0xf500


// KSS Bootstrap
static UINT8 kss_boot[] = {
		// Disable interrupts and change IM
		0xf3, 0xed, 0x56,		// di, im 1

		// Send the init command to the driver
		0x3e, 0x31,			// ld a, soundcode
		0xCD, 0x00, 0x00,   // call init

		// Turn interrupts back on
		0xfb,

		// Halt and wait for the next NMI
		0x76,

		// When an NMI is signalled, call play, then halt the CPU again
		0xcd, 0x00, 0x00, // call play
		0x18, 0xfa,		  // jr -5
};

void CKSSDriver::Reset() {

	memset(prg_space.memspace,0,0xffff);
	memset(prg_space.memspace,0xc9, 0x3fff);
	memcpy(prg_space.memspace + 1, wrtpsg,8);
	memcpy(prg_space.memspace + 0x93, j_wrtpsg, 3);	
	memcpy(prg_space.memspace + 9, rdpsg,5);
	memcpy(prg_space.memspace + 0x96, j_rdpsg, 3);	


	// Set the Z80 to boot from code somewhere in memory
	Msg("Writing boot code\n");
	prg_space.memspace[0x66]  =  0xed; // return from interrupts
	prg_space.memspace[0x67]  =  0x45; // return from interrupts


	// Disable and modify interrupts
	prg_space.memspace[KSS_DRIVER_START + 0] = 0xf3; // di
	prg_space.memspace[KSS_DRIVER_START + 1] = 0xed; // im 1
	prg_space.memspace[KSS_DRIVER_START + 2] = 0x56; 
	
	// Init the song
	prg_space.memspace[KSS_DRIVER_START + 3] = 0x3e; // ld a, Soundcode
	prg_space.memspace[KSS_DRIVER_START + 4] = 0x31; 
	prg_space.memspace[KSS_DRIVER_START + 5] = 0xCD; // call Init
	prg_space.memspace[KSS_DRIVER_START + 6] = header->init_addr & 0xff;
	prg_space.memspace[KSS_DRIVER_START + 7] = (header->init_addr & 0xff00) >> 8;

	// Enable Interrupts
	prg_space.memspace[KSS_DRIVER_START + 8] = 0xfb;

	// Halt the CPU, wait for an interrupt, call play, rinse and repeat
	prg_space.memspace[KSS_DRIVER_START + 9]  = 0x76; // halt
	prg_space.memspace[KSS_DRIVER_START + 10]  = 0xcd;
	prg_space.memspace[KSS_DRIVER_START + 11]  =  header->play_addr & 0xff;
	prg_space.memspace[KSS_DRIVER_START + 12]  = (header->play_addr & 0xff00) >> 8;
	prg_space.memspace[KSS_DRIVER_START + 13]  = 0x18; // jr -5
	prg_space.memspace[KSS_DRIVER_START + 14]  = 0xfa;

	// Reset the Z80
	z80->Reset();
	z80->SetRegister("pc",KSS_DRIVER_START);
	z80->SetRegister("sp",0xf380);

	// Copy stuff
	memcpy(prg_space.memspace + header->load_offset, main_code->GetRawMemory(), header->load_size);

	// Reset devices
	ay->Reset();
}

int CKSSDriver::Init(CHardware* hard) {

	CTimer* vsync = new CTimer(hard);

	Msg("Initializing Z80...\n");
	this->z80 = CPUFactory("Z80");

	if (!z80) {
		Msg("Cannot init hardware because Z80 couldn't be initialized!\n");
		return 1;
	}
	z80->Init(8000000); // 3.58 MHz

	Msg("Setting up MMUs...\n");
	z80->AssignMMU(0, &this->prg_space);
	z80->AssignMMU(1, &this->io_space);

	

	// Tell the program MMU to write stuff
	

	// ******** Setup Sound Generators

	Msg("Starting up AY8910\n");
	ay = SoundDeviceFactory("AY8910");
	ay->Init(hard->mixer, 3579545);

	
	Msg("Starting up SCC\n");

	scc = new CKonamiSCC;
	scc->Init(hard->mixer, 3579545);
	Msg("Initialized OK\n");

	// Init the vblank timer
	vsync->SetupAsVBlank(1.0 / 60.0, z80, 1); // send IRQs every 60 Hz

	// Throw stuff back to hardware
	hard->AddCpu(z80);
	hard->AddSound(ay);
	hard->AddSound(scc);
	hard->AddTimer(vsync);

	// Send important information to the MMUs
	io_space.ay = this->ay;
	io_space.banked_code = this->bank_code;
	prg_space.scc = this->scc;

	if (header->banksize == 0x4000) {
		io_space.bank_size = header->banksize;
		io_space.first_bank = header->first_bank;
		io_space.isUsing16KBBanks = 1;
	}

	// Link the MMUs
	io_space.prgspace = &prg_space;

	Reset(); // Copy code

	Msg("KSS booted. Send us a sound code!\n");
	return 0;
}

void CKSSDriver::FreeUnusedResources() {

}

void CKSSDriver::SetSubsong(int song) {
	// set the code and then NMI the CPU
	song &= 0xff;
	prg_space.memspace[0xf504] = song;
	//Reset();
}

void CKSSDriver::Close() {

}

DRIVER_FILETYPE_MAP_START(CKSSDriver)
	DRIVER_FILETYPE_MAP("kss")
DRIVER_FILETYPE_MAP_END

DRIVER_FACTORY("kss","MSX/SMS KSS",DRIVER_INTERFACE_VERSION_1,CKSSDriver);
